using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using InterfaceWeaver.AopCore;
using InterfaceWeaver.Component;
using Roslyn.Compilers.CSharp;

namespace InterfaceWeaver.CustomTool
{
    public class MapCommandAspectHandler : AspectHandlerBase<MapCommandAttribute>
    {
        /// <summary>
        /// Regarding all the handler answering preview step, this step determines whether there are some aspect handler that should be removed from next live cycle.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <param name="indirectAspectHandler"> </param>
        /// <param name="indirectAttributeSyntax"> </param>
        /// <returns></returns>
        public override bool IsIndirectAspectsCompliantOnInterface(AspectDispatcher context, AttributeSyntax attributeSyntax, IAspectHandler indirectAspectHandler, AttributeSyntax indirectAttributeSyntax)
        {
            return !(indirectAspectHandler is MapDependencyPropertyAspectHandler);
        }

        /// <summary>
        /// Handles the on property preview.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <returns></returns>
        public override bool HandleOnPropertyPreview(AspectDispatcher context, AttributeSyntax attributeSyntax)
        {
            return base.HandleOnPropertyPreview(context, attributeSyntax) 
                && ((PropertyDeclarationSyntax)context.ParsedCodeContext.CurrentMemberSyntax).IsOfType<ICommand>();
        }

        /// <summary>
        /// Regarding all the handler answering preview step, this step determines whether there are some aspect handler that should be removed from next live cycle.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <param name="indirectAspectHandler"> </param>
        /// <param name="indirectAttributeSyntax"> </param>
        /// <returns></returns>
        public override bool IsIndirectAspectsCompliantOnProperty(AspectDispatcher context, AttributeSyntax attributeSyntax, IAspectHandler indirectAspectHandler, AttributeSyntax indirectAttributeSyntax)
        {
            return !(indirectAspectHandler is MapDependencyPropertyAspectHandler
                || indirectAspectHandler is ImplementIEditableObjectAspectHandler);
        }

        /// <summary>
        /// Handles the on member.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <param name="propertyDeclarationSyntax"> </param>
        /// <param name="handlerStreams"> </param>
        /// <returns>
        /// True if the aspect is handled, false otherwise
        /// </returns>
        public override void HandleOnProperty(AspectDispatcher context, AttributeSyntax attributeSyntax, PropertyDeclarationSyntax propertyDeclarationSyntax, IDictionary<PropertyDeclarationSyntax, AspectTuple[]> handlerStreams)
        {
            if((propertyDeclarationSyntax).IsOfType<ICommand>())
            {
                var currentProperty = context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.CurrentCodeMemberPropertyDeclaration;
                var currentField = context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.CurrentCodeMemberFieldDeclaration;
                var initializeCommandsMethod = context.WeavedCodeContext.CurrentTypeDeclaration.EnsureInitializeCommandsMethodDeclaration(!context.ParsedCodeContext.CurrentInterfaceSyntax.ShouldSkipConstructorDeclaration());

                // Method to call in the functional instance
                var methodName = attributeSyntax.HasParameter("MethodName") 
                    ? attributeSyntax.GetParameterValue<string>("MethodName") 
                    : this.DetermineAutoFindExecuteMethodName(currentProperty.Name);

                // Determining owner instance
                CodeExpression functionalInstanceReference = null;
                if (attributeSyntax.HasParameter("OwnerType"))
                {
                    // Targetted functional instance
                    var ownerTypeArgument = attributeSyntax.GetParameterTypeOf("OwnerType");
                    var instanceField = context.WeavedCodeContext.CurrentTypeDeclaration.EnsureFieldInstanceDeclaration(
                            ownerTypeArgument,
                            string.Format("This assumes the type [{0}] has a constructor taken into parameters a reference to current instance.", ownerTypeArgument.ToFullString().Trim()),
                            new CodeThisReferenceExpression());

                    functionalInstanceReference = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), instanceField.Name);
                }
                else
                {
                    functionalInstanceReference = new CodeThisReferenceExpression();
                }

                var targettedDelegateReference = new CodeFieldReferenceExpression(functionalInstanceReference, methodName);

                // Command mapper instanciation
                var commandMapperInstance = attributeSyntax.HasParameter("ParameterType") 
                    ? new CodeObjectCreateExpression(string.Format("{0}<{1}>", typeof(CommandMapper).FullName, attributeSyntax.GetParameterTypeOfName("ParameterType")), targettedDelegateReference) 
                    : new CodeObjectCreateExpression(typeof(CommandMapper), targettedDelegateReference);

                if(attributeSyntax.HasParameter("HasCanExecute"))
                {
                    // Method to call in the functional instance
                    var canExecuteMethodName = attributeSyntax.HasParameter("CanExecuteMethodName") 
                        ? attributeSyntax.GetParameterValue<string>("CanExecuteMethodName") 
                        : this.DetermineAutoFindCanExecuteMethodName(currentProperty.Name);

                    var targettedCanExecuteDelegateReference = new CodeFieldReferenceExpression(functionalInstanceReference, canExecuteMethodName);
                    commandMapperInstance.Parameters.Add(targettedCanExecuteDelegateReference);
                }

                var mapperAssignement = new CodeAssignStatement(
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), currentField.Name),
                    commandMapperInstance);

                // Add a new line before the comment
                if (initializeCommandsMethod.Statements.Count > 0)
                {
                    initializeCommandsMethod.Statements.Add(new CodeSnippetStatement(string.Empty));
                }

                // Insert a comment explaining what is mapped here
                initializeCommandsMethod.Statements.Add(new CodeCommentStatement(
                    string.Format("This assumes the hosting type contains an accessible method [{0}]", methodName)));

                // Add the mapping code the initilizeCOmmand method
                initializeCommandsMethod.Statements.Add(mapperAssignement);
            }
        }


        /// <summary>
        /// Determines the name of the auto find execute method.
        /// </summary>
        /// <param name="commandName">Name of the command.</param>
        /// <returns></returns>
        private string DetermineAutoFindExecuteMethodName(string commandName)
        {
            if (commandName.EndsWith("Command"))
            {
                return commandName.Remove(commandName.Length - "Command".Length);
            }

            if (commandName.StartsWith("Command"))
            {
                return commandName.Remove(0, "Command".Length);
            }
            
            return commandName;
        }


        /// <summary>
        /// Determines the name of the auto find can execute method.
        /// </summary>
        /// <param name="commandName">Name of the command.</param>
        /// <returns></returns>
        private string DetermineAutoFindCanExecuteMethodName(string commandName)
        {
            if (commandName.EndsWith("Command"))
            {
                return "Can" + commandName.Remove(commandName.Length - "Command".Length);
            }

            if(commandName.StartsWith("Command"))
            {
                return "Can" + commandName.Remove(0, "Command".Length);
            }

            return "Can" + commandName;
        }
    }
}